package top.codef.secure.components;

import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.DeferredSecurityContext;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.SecurityContextRepository;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import top.codef.secure.abstracts.MightyUserDetails;
import top.codef.secure.abstracts.UserDetailDecorator;
import top.codef.secure.properties.CerberusRestHeaderProperties;

@SuppressWarnings("deprecation")
public class RedisSecurityContextRepository implements SecurityContextRepository {

	private final RedisTemplate<String, Object> securityRedisTemplate;

	private final String principalKey = "principal";

	private final String credentialKey = "credential";

	private final CerberusRestHeaderProperties cerberusRestHeaderProperties;

	private UserDetailDecorator userDetailDecorator;

	public static final String refreshKey = "refresh_authentication";

	public static final String CURRENT_TOKEN = "CURRENT_TOKEN";

	public RedisSecurityContextRepository(RedisTemplate<String, Object> securityRedisTemplate,
			CerberusRestHeaderProperties cerberusRestHeaderProperties) {
		this.securityRedisTemplate = securityRedisTemplate;
		this.cerberusRestHeaderProperties = cerberusRestHeaderProperties;
	}

	private String credentialKey(String token) {
		return String.format("%s:%s:%s", cerberusRestHeaderProperties.getRedisKeyPrefix(), credentialKey, token);
	}

	public RedisTemplate<String, Object> getSecurityRedisTemplate() {
		return securityRedisTemplate;
	}

	public UserDetailDecorator getUserDetailDecorator() {
		return userDetailDecorator;
	}

	public void setUserDetailDecorator(UserDetailDecorator userDetailDecorator) {
		this.userDetailDecorator = userDetailDecorator;
	}

	public String getPrincipalKey() {
		return principalKey;
	}

	public CerberusRestHeaderProperties getCerberusRestHeaderProperties() {
		return cerberusRestHeaderProperties;
	}

	public String getCredentialKey() {
		return credentialKey;
	}

	private String principalKey(String username) {
		return String.format("%s:%s:%s", cerberusRestHeaderProperties.getRedisKeyPrefix(), principalKey, username);
	}

	@Override
	public DeferredSecurityContext loadDeferredContext(HttpServletRequest request) {
		SecurityContext securityContext = SecurityContextHolder.getContext();
		Authentication authentication = securityContext.getAuthentication();
		if (authentication == null) {
			String token = request.getHeader(cerberusRestHeaderProperties.getHeaderTokenKey());
			if (token != null) {
				authentication = (Authentication) securityRedisTemplate.opsForValue().get(credentialKey(token));
				securityContext.setAuthentication(authentication);
				request.setAttribute(CURRENT_TOKEN, token);
//				System.out.println("获取：" + request.getServletPath() + "-->" + token + "-->"
//						+ (authentication == null ? null : authentication.getPrincipal()));
			}
		}
		request.setAttribute("tokenChecked", authentication);
//		Supplier<SecurityContext> supplier = () -> securityContext;
		return new DeferredSecurityContext() {

			@Override
			public SecurityContext get() {
				return securityContext;
			}

			@Override
			public boolean isGenerated() {
				return true;
			}
		};
	}

//	@Override
//	public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
//	
//	}

	@Override
	public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
		Authentication authentication = context.getAuthentication();
		if (authentication == null || authentication.getPrincipal() == null
				|| authentication.getPrincipal().equals("anonymousUser"))
			return;
		if (!isContextSaved(request, context)) {
			String token = UUID.randomUUID().toString().replace("-", "");
			UserDetails userDetails = (UserDetails) authentication.getPrincipal();
			if (userDetails instanceof MightyUserDetails && userDetailDecorator != null) {
				MightyUserDetails mightyUserDetails = (MightyUserDetails) userDetails;
				userDetailDecorator.decorate(mightyUserDetails);
			}
			if (cerberusRestHeaderProperties.isEnableSso())
				singleUserCheckSave(userDetails.getUsername(), token);
			response.setHeader(cerberusRestHeaderProperties.getHeaderTokenKey(), token);
			ValueOperations<String, Object> operations = securityRedisTemplate.opsForValue();
			operations.set(credentialKey(token), authentication, cerberusRestHeaderProperties.getTimeout());
			request.setAttribute("tokenChecked", authentication);
			request.setAttribute(CURRENT_TOKEN, token);
//			System.out.println("保存authentication:" + request.getServletPath() + "-->" + token + "-->" + authentication);
		} else {
			Boolean needRefresh = (Boolean) request.getAttribute(refreshKey);
			if (needRefresh != null && needRefresh) {
				String token = (String) request.getAttribute(CURRENT_TOKEN);
				if (token != null) {
					ValueOperations<String, Object> operations = securityRedisTemplate.opsForValue();
					operations.set(credentialKey(token), authentication, cerberusRestHeaderProperties.getTimeout());
//					System.out.println("刷新authentication:" + request.getServletPath() + "-->" + token + "-->"
//							+ authentication.getPrincipal());
				}
			}
		}
		if (response.isCommitted())
			request.removeAttribute("tokenChecked");
	}

	private void singleUserCheckSave(String username, String token) {
		String principalKey = principalKey(username);
		ValueOperations<String, Object> valueOperations = securityRedisTemplate.opsForValue();
		String originalToken = (String) valueOperations.get(principalKey);
		if (originalToken != null)
			securityRedisTemplate.delete(credentialKey(originalToken));
		valueOperations.set(principalKey, token, cerberusRestHeaderProperties.getTimeout());
	}

	@Override
	public boolean containsContext(HttpServletRequest request) {
		String token = request.getHeader(cerberusRestHeaderProperties.getHeaderTokenKey());
		if (token != null)
			return securityRedisTemplate.hasKey(credentialKey(token));
		return false;
	}

	private boolean isContextSaved(HttpServletRequest request, SecurityContext context) {
		Authentication authentication = context.getAuthentication();
		if (request.getAttribute("tokenChecked") != authentication)
			return false;
		else {
			String token = request.getHeader(cerberusRestHeaderProperties.getHeaderTokenKey());
			Duration timeout = cerberusRestHeaderProperties.getTimeout();
			securityRedisTemplate.expire(credentialKey(token), timeout.getSeconds(), TimeUnit.SECONDS);
			if (cerberusRestHeaderProperties.isEnableSso()) {
				UserDetails userDetails = (UserDetails) authentication.getPrincipal();
				securityRedisTemplate.expire(principalKey(userDetails.getUsername()), timeout.getSeconds(),
						TimeUnit.SECONDS);
			}
			return true;
		}
	}

	public void clearContext(HttpServletRequest request, SecurityContext context) {
		Authentication authenticattion = context.getAuthentication();
		if (request.getAttribute("tokenChecked") == authenticattion) {
			if (authenticattion == null)
				return;
			UserDetails userDetails = (UserDetails) authenticattion.getPrincipal();
			securityRedisTemplate.delete(principalKey(userDetails.getUsername()));
		}
		String token = request.getHeader(cerberusRestHeaderProperties.getHeaderTokenKey());
		if (token != null)
			securityRedisTemplate.delete(credentialKey(token));
		context.setAuthentication(null);
	}

	@Override
	public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
		// TODO Auto-generated method stub
		return null;
	}
}